﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Fuse8.DomainFramework.CacheService.EntityCacheKeyServices;
using Fuse8.DomainFramework.Configuration.Cache;
using Fuse8.DomainFramework.DataAccess;
using Fuse8.DomainFramework.Domain;

namespace Fuse8.DomainFramework.CacheService
{
    /// <summary>
    /// 
    /// </summary>
    public class DefaultCacheKeyService : ICacheKeyService
    {
		private static string _cachePrefix;

		private static readonly Dictionary<Func<Type, bool>, IEntityCacheKeyService> _cacheKeyServices = new Dictionary<Func<Type, bool>, IEntityCacheKeyService> ();

    	private const string AddTagFormat = "{0}_add_tag:{1}";
		private const string DependencyTagFormat = "{0}_dependency_tag:{1}";

    	/// <summary>
    	/// Register cache key service for entities of type T
    	/// </summary>
    	/// <param name="predicate"></param>
    	/// <param name="cacheService">Cache service</param>
		public static void RegisterCacheService(Func<Type, bool> predicate, IEntityCacheKeyService cacheService)
		{
			_cacheKeyServices[predicate] = cacheService;
		}

		static DefaultCacheKeyService()
		{
			RegisterCacheService(p => p.GetInterfaces().Any(q => q.IsGenericType && q.GetGenericTypeDefinition() == typeof(IEntityCommand<>)), new CommandCacheKeyService());
			RegisterCacheService(p => typeof(IEntityObject).IsAssignableFrom(p), new EntityObjectCacheKeyService());
			RegisterCacheService(p => typeof(Expression).IsAssignableFrom(p), new ExpressionObjectCacheKeyService());
		}

		/// <summary>
		/// Prefix for cache key. Must be equal for all entities, if modified default
		/// </summary>
		public virtual string CachePrefix
		{
			get
			{
				return _cachePrefix ?? (_cachePrefix = CacheServiceConfigurationManager.Config != null && !string.IsNullOrEmpty(CacheServiceConfigurationManager.Config.KeyPrefix) ? CacheServiceConfigurationManager.Config.KeyPrefix : "default");
			}			
		}

		/// <summary>
		/// 
		/// </summary>
		protected static readonly string DependencyCacheKey = "{1}_dependencyList_{0}";

    	/// <summary>
    	/// 
    	/// </summary>        
    	/// <returns></returns>
    	public virtual string GenerateCacheKey(params object[] parameters)
		{
			if (parameters == null || !parameters.Any())
			{
				return string.Format(CultureInfo.InvariantCulture, "{0}_All", CachePrefix);
			}

			return string.Format(CultureInfo.InvariantCulture, "{0}_{1}", CachePrefix, string.Join("_", parameters.Where(p=>p != null).Select(GenerateCacheKey).ToArray()));
		}

		private static string GenerateCacheKey(object obj)
		{
			var cacheService = _cacheKeyServices.Where(p => p.Key(obj.GetType())).Select(p => p.Value).FirstOrDefault();
			return cacheService != null ? cacheService.GenerateCacheKey(obj) : obj.ToString();
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public string GenerateDependencyCacheKey(IEntityObject entity)
        {
			return string.Format(CultureInfo.CurrentCulture, DependencyCacheKey, entity.TypeParameter, CachePrefix);
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="tag"></param>
		/// <returns></returns>
		public string GenerateDependencyCacheKeyTag(string tag)
		{
			return string.Format(CultureInfo.InvariantCulture, DependencyTagFormat, CachePrefix, tag);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="cacheKey"></param>
		/// <returns></returns>
    	public string GenerateAddCacheKyeTag(string cacheKey)
    	{
			return string.Format(CultureInfo.InvariantCulture, AddTagFormat, CachePrefix, cacheKey);
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public IList<string> GenerateCacheKeysForLinkedDependency<TEntity>(TEntity entity)
            where TEntity : class, IEntityObject
        {
            IList<string> result = new List<string>();

            var query = typeof (TEntity).GetProperties()
                .Where(p => p.GetCustomAttributes(typeof (DependencyLinkAttribute), true).Length > 0);

            foreach (var item in query)
            {
                var obj = item.GetValue(entity, BindingFlags.GetProperty, null, null, null);

                if (obj != null)
                {
                    if (obj is IEnumerable)
                    {
                        var propertyType = item.PropertyType;
                        if (propertyType.IsGenericType && propertyType.GetGenericArguments()[0].GetInterfaces().Any(p => p.Equals(typeof (IEntityObject))))
                        {
                            var service = new CacheKeyFacade ();
                            var list = obj as IEnumerable;
                            foreach (var listItem in list)
                            {
                                result.Add(service.GenerateCacheKey(listItem));
                            }
                        }
                    }
                    else
                    {
						result.Add(new CacheKeyFacade().GenerateCacheKey(obj));
                    }
                }
            }

            return result;
        }
    }
}